#
# Copyright (c) 2019, Bojan Vitnik <bvitnik@mainstream.rs>
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
# SPDX-License-Identifier: GPL-3.0-or-later

from __future__ import annotations


import pytest

from .FakeAnsibleModule import FailJsonException
from .common import fake_xenapi_ref, testcase_bad_xenapi_refs


testcase_wait_for_vm_ip_address_bad_power_states = {
    "params": [
        "Halted",
        "Paused",
        "Suspended",
        "Other",
    ],
    "ids": [
        "state-halted",
        "state-paused",
        "state-suspended",
        "state-other",
    ],
}

testcase_wait_for_vm_ip_address_bad_guest_metrics = {
    "params": [
        ("OpaqueRef:NULL", {"networks": {}}),
        (fake_xenapi_ref("VM_guest_metrics"), {"networks": {}}),
    ],
    "ids": [
        "vm_guest_metrics_ref-null, no-ip",
        "vm_guest_metrics_ref-ok, no-ip",
    ],
}

testcase_wait_for_task_all_statuses = {
    "params": [
        ("Success", ""),
        ("Failure", "failure"),
        ("Cancelling", "cancelling"),
        ("Cancelled", "cancelled"),
        ("Other", "other"),
    ],
    "ids": [
        "task-success",
        "task-failure",
        "task-cancelling",
        "task-cancelled",
        "task-other",
    ],
}


@pytest.mark.parametrize("vm_ref", testcase_bad_xenapi_refs["params"], ids=testcase_bad_xenapi_refs["ids"])  # type: ignore
def test_wait_for_vm_ip_address_bad_vm_ref(fake_ansible_module, xenserver, vm_ref):
    """Tests failure on bad vm_ref."""
    with pytest.raises(FailJsonException) as exc_info:
        xenserver.wait_for_vm_ip_address(fake_ansible_module, vm_ref)

    assert exc_info.value.kwargs["msg"] == "Cannot wait for VM IP address. Invalid VM reference supplied!"


def test_wait_for_vm_ip_address_xenapi_failure(mock_xenapi_failure, xenserver, fake_ansible_module):
    """Tests catching of XenAPI failures."""
    with pytest.raises(FailJsonException) as exc_info:
        xenserver.wait_for_vm_ip_address(fake_ansible_module, fake_xenapi_ref("VM"))

    assert exc_info.value.kwargs["msg"] == f"XAPI ERROR: {mock_xenapi_failure[1]}"


@pytest.mark.parametrize(
    "bad_power_state",
    testcase_wait_for_vm_ip_address_bad_power_states["params"],
    ids=testcase_wait_for_vm_ip_address_bad_power_states["ids"],
)
def test_wait_for_vm_ip_address_bad_power_state(mocker, fake_ansible_module, XenAPI, xenserver, bad_power_state):
    """Tests failure on bad power state."""
    mocked_xenapi = mocker.patch.object(XenAPI.Session, "xenapi", create=True)

    mocked_returns = {
        "VM.get_power_state.return_value": bad_power_state,
    }

    mocked_xenapi.configure_mock(**mocked_returns)

    with pytest.raises(FailJsonException) as exc_info:
        xenserver.wait_for_vm_ip_address(fake_ansible_module, fake_xenapi_ref("VM"))

    assert exc_info.value.kwargs["msg"] == (
        f"Cannot wait for VM IP address when VM is in state '{xenserver.xapi_to_module_vm_power_state(bad_power_state.lower())}'!"
    )


@pytest.mark.parametrize(
    "bad_guest_metrics_ref, bad_guest_metrics",
    testcase_wait_for_vm_ip_address_bad_guest_metrics["params"],  # type: ignore
    ids=testcase_wait_for_vm_ip_address_bad_guest_metrics["ids"],  # type: ignore
)
def test_wait_for_vm_ip_address_timeout(
    mocker, fake_ansible_module, XenAPI, xenserver, bad_guest_metrics_ref, bad_guest_metrics
):
    """Tests timeout."""
    mocked_xenapi = mocker.patch.object(XenAPI.Session, "xenapi", create=True)

    mocked_returns = {
        "VM.get_power_state.return_value": "Running",
        "VM.get_guest_metrics.return_value": bad_guest_metrics_ref,
        "VM_guest_metrics.get_record.return_value": bad_guest_metrics,
    }

    mocked_xenapi.configure_mock(**mocked_returns)

    mocker.patch("time.sleep")

    with pytest.raises(FailJsonException) as exc_info:
        xenserver.wait_for_vm_ip_address(fake_ansible_module, fake_xenapi_ref("VM"), timeout=1)

    assert exc_info.value.kwargs["msg"] == "Timed out waiting for VM IP address!"


def test_wait_for_vm_ip_address(mocker, fake_ansible_module, XenAPI, xenserver):
    """Tests regular invocation."""
    mocked_xenapi = mocker.patch.object(XenAPI.Session, "xenapi", create=True)

    # This mock simulates regular VM IP acquirement lifecycle:
    #
    # 1) First, no guest metrics are available because VM is not yet fully
    #    booted and guest agent is not yet started.
    # 2) Next, guest agent is started and guest metrics are available but
    #    IP address is still not acquired.
    # 3) Lastly, IP address is acquired by VM on its primary VIF.
    mocked_returns = {
        "VM.get_power_state.return_value": "Running",
        "VM.get_guest_metrics.side_effect": [
            "OpaqueRef:NULL",
            fake_xenapi_ref("VM_guest_metrics"),
            fake_xenapi_ref("VM_guest_metrics"),
        ],
        "VM_guest_metrics.get_record.side_effect": [
            {
                "networks": {},
            },
            {
                "networks": {
                    "0/ip": "192.168.0.1",
                    "1/ip": "10.0.0.1",
                },
            },
        ],
    }

    mocked_xenapi.configure_mock(**mocked_returns)

    mocker.patch("time.sleep")

    fake_guest_metrics = xenserver.wait_for_vm_ip_address(fake_ansible_module, fake_xenapi_ref("VM"))

    assert fake_guest_metrics == mocked_returns["VM_guest_metrics.get_record.side_effect"][1]


@pytest.mark.parametrize("task_ref", testcase_bad_xenapi_refs["params"], ids=testcase_bad_xenapi_refs["ids"])  # type: ignore
def test_wait_for_task_bad_task_ref(fake_ansible_module, xenserver, task_ref):
    """Tests failure on bad task_ref."""
    with pytest.raises(FailJsonException) as exc_info:
        xenserver.wait_for_task(fake_ansible_module, task_ref)

    assert exc_info.value.kwargs["msg"] == "Cannot wait for task. Invalid task reference supplied!"


def test_wait_for_task_xenapi_failure(mock_xenapi_failure, fake_ansible_module, xenserver):
    """Tests catching of XenAPI failures."""
    with pytest.raises(FailJsonException) as exc_info:
        xenserver.wait_for_task(fake_ansible_module, fake_xenapi_ref("task"))

    assert exc_info.value.kwargs["msg"] == f"XAPI ERROR: {mock_xenapi_failure[1]}"


def test_wait_for_task_timeout(mocker, fake_ansible_module, XenAPI, xenserver):
    """Tests timeout."""
    mocked_xenapi = mocker.patch.object(XenAPI.Session, "xenapi", create=True)

    mocked_returns = {
        "task.get_status.return_value": "Pending",
        "task.destroy.return_value": None,
    }

    mocked_xenapi.configure_mock(**mocked_returns)

    mocker.patch("time.sleep")

    fake_result = xenserver.wait_for_task(fake_ansible_module, fake_xenapi_ref("task"), timeout=1)

    mocked_xenapi.task.destroy.assert_called_once()
    assert fake_result == "timeout"


@pytest.mark.parametrize(
    "task_status, result",
    testcase_wait_for_task_all_statuses["params"],  # type: ignore
    ids=testcase_wait_for_task_all_statuses["ids"],  # type: ignore
)
def test_wait_for_task(mocker, fake_ansible_module, XenAPI, xenserver, task_status, result):
    """Tests regular invocation."""
    mocked_xenapi = mocker.patch.object(XenAPI.Session, "xenapi", create=True)

    # Mock will first return Pending status and on second invocation it will
    # return one of possible final statuses.
    mocked_returns = {
        "task.get_status.side_effect": [
            "Pending",
            task_status,
        ],
        "task.destroy.return_value": None,
    }

    mocked_xenapi.configure_mock(**mocked_returns)

    mocker.patch("time.sleep")

    fake_result = xenserver.wait_for_task(fake_ansible_module, fake_xenapi_ref("task"))

    mocked_xenapi.task.destroy.assert_called_once()
    assert fake_result == result
